package post

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"mall/common/globalkey"
	"mall/common/modelx"
	"mall/common/moneyx"
	"mall/common/mqueuex"
	"mall/common/utils/pointy"
	"mall/service/backend/common/errorx"
	"mall/service/backend/common/i18n"
	"mall/service/blog/rpc/blogclient"
	"mall/service/forum/model"
	"mall/service/forum/rpc/internal/svc"
	"mall/service/forum/rpc/internal/utils/errorhandler"
	"mall/service/forum/rpc/types/forum"
	"mall/service/user/rpc/userclient"

	"github.com/sirupsen/logrus"
	"github.com/zeromicro/go-zero/core/logx"
)

type BackendBatchUpdatePostLogic struct {
	ctx    context.Context
	svcCtx *svc.ServiceContext
	logx.Logger
}

func NewBackendBatchUpdatePostLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BackendBatchUpdatePostLogic {
	return &BackendBatchUpdatePostLogic{
		ctx:    ctx,
		svcCtx: svcCtx,
		Logger: logx.WithContext(ctx),
	}
}

func (l *BackendBatchUpdatePostLogic) BackendBatchUpdatePost(in *forum.BackendPostListReq) (*forum.BaseResp, error) {
	if in.Ids == nil || len(strings.TrimSpace(*in.Ids)) == 0 {
		return nil, errorx.NewInvalidArgumentError(i18n.ValidationError)
	}

	filterMap := make(map[string]map[string][]any)
	if vals := modelx.UniqueInterfaces(strings.TrimSpace(*in.Ids), 64); len(vals) > 0 {
		filterMap[model.ForumPostColumns.PostID] = map[string][]any{
			"in": vals,
		}
	} else {
		return nil, errorx.NewInvalidArgumentError(i18n.ValidationError)
	}

	var status int32
	var sticky int32
	var noticeType int32
	var restricted bool // 数据有效
	var replyRestricted bool
	var recMedia bool
	var recRating bool
	var recFeed bool
	if in.Type != nil {
		switch int32(*in.Type) {
		case globalkey.ForumModTypeReview, globalkey.ForumModTypePhoto: // 审核香评、图片
			if in.Status != nil {
				switch int32(*in.Status) {
				case globalkey.StatusApproved: // 审核通过
					filterMap[model.ForumPostColumns.PostStatus] = map[string][]any{
						"in": []any{globalkey.StatusPending, globalkey.StatusCheckSuccess},
					}
					status = globalkey.StatusApproved
					noticeType = globalkey.ForumNoticeTypeApproved // 审核通过
					restricted = true
					if int32(*in.Type) == globalkey.ForumModTypePhoto {
						recMedia = true
					} else if int32(*in.Type) == globalkey.ForumModTypeReview {
						recRating = true
					}
				case globalkey.StatusRejected: // 审核驳回
					filterMap[model.ForumPostColumns.PostStatus] = map[string][]any{
						"in": []any{globalkey.StatusPending, globalkey.StatusCheckSuccess, globalkey.StatusCheckFailed},
					}
					status = globalkey.StatusRejected
					noticeType = globalkey.ForumNoticeTypeRejected // 审核驳回
					restricted = true
				}
			}
		case globalkey.ForumModTypeReply: // 审核回答
			if in.Status != nil {
				switch int32(*in.Status) {
				case globalkey.StatusApproved: // 审核通过
					filterMap[model.ForumPostColumns.PostStatus] = map[string][]any{
						"in": []any{globalkey.StatusPending, globalkey.StatusCheckSuccess},
					}
					status = globalkey.StatusApproved
					noticeType = globalkey.ForumNoticeTypeApproved // 审核通过
					replyRestricted = true
					recFeed = true
				case globalkey.StatusRejected: // 审核驳回
					filterMap[model.ForumPostColumns.PostStatus] = map[string][]any{
						"in": []any{globalkey.StatusPending, globalkey.StatusCheckSuccess, globalkey.StatusCheckFailed},
					}
					status = globalkey.StatusRejected
					noticeType = globalkey.ForumNoticeTypeRejected // 审核驳回
					replyRestricted = true
				}
			}
		case globalkey.ForumModTypeReviewSticky: // 推荐香评、图片
			if in.Sticky != nil {
				if *in.Sticky > 0 {
					filterMap[model.ForumPostColumns.PostStatus] = map[string][]any{
						"equalTo": []any{globalkey.StatusApproved},
					}
					filterMap[model.ForumPostColumns.Sticky] = map[string][]any{
						"equalTo": []any{0},
					}
					sticky = globalkey.StatusSticky              // 推荐
					noticeType = globalkey.ForumNoticeTypeSticky // 推荐
					restricted = true
				} else {
					filterMap[model.ForumPostColumns.PostStatus] = map[string][]any{
						"equalTo": []any{globalkey.StatusApproved},
					}
					filterMap[model.ForumPostColumns.Sticky] = map[string][]any{
						"equalTo": []any{1},
					}
					sticky = globalkey.StatusUnsticky              // 取消推荐
					noticeType = globalkey.ForumNoticeTypeUnsticky // 取消推荐
					restricted = true
				}
			}
		}
	}

	resp := &forum.BaseResp{}

	if !restricted && !replyRestricted {
		return resp, nil
	}

	res, _, err := l.svcCtx.Post.GetList(l.ctx, model.M{}, -1, filterMap)
	if err != nil {
		return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
	}

	if len(res) == 0 {
		return resp, nil
	}

	idPostMap := make(map[uint64]*model.ForumPost)
	idParentMap := make(map[uint64]uint64)
	idTopicMap := make(map[uint64]int)
	idAuthorMap := make(map[uint64]uint64)
	idCountMap := make(map[uint64]uint)
	titleMap := make(map[uint64]string)
	var pids []uint64 // post ids
	var ids []any
	var sids []string
	var tids []any
	for _, v := range res {
		idAuthorMap[v.PostID] = v.PostAuthorID
		idCountMap[v.PostID] = v.PhotoCount
		excerptRune := []rune(v.PostContent)
		if len(excerptRune) > 20 {
			v.PostContent = string(excerptRune[0:17]) + "..."
		}
		idPostMap[v.PostID] = v
		pids = append(pids, v.PostID)
		ids = append(ids, v.PostID)
		if replyRestricted && v.TopicID > 0 {
			tids = append(tids, v.TopicID)
			if _, ok := idTopicMap[v.TopicID]; !ok {
				idTopicMap[v.TopicID] = 1
			} else {
				idTopicMap[v.TopicID] += 1
			}
		} else if restricted && v.ParentID > 0 { // 香水ID
			sids = append(sids, strconv.FormatInt(int64(v.ParentID), 10))
			idParentMap[v.PostID] = v.ParentID
		}
	}

	if len(sids) > 0 {
		list, err := l.svcCtx.BlogRpc.BookList(l.ctx, &blogclient.BookListReq{
			Preload:  "blogBook:Id,Title",
			EntryIds: strings.Join(sids, ","),
		})
		if err != nil {
			logrus.Info(fmt.Sprintf("BackendBatchUpdatePost BlogRpc.BookList err: %+v", err))
			return nil, err
		}

		if len(list.Data) > 0 {
			for _, v := range list.Data {
				titleRune := []rune(v.Title)
				if len(titleRune) > 20 {
					titleMap[uint64(v.Id)] = string(titleRune[0:17]) + "..."
				} else {
					titleMap[uint64(v.Id)] = v.Title
				}
			}
		}
	}

	if len(tids) > 0 {
		topicFilterMap := make(map[string]map[string][]any)
		topicFilterMap[model.ForumTopicColumns.TopicID] = map[string][]any{
			"in": tids,
		}
		list, _, err := l.svcCtx.Topic.GetList(l.ctx, model.M{}, -1, topicFilterMap)
		if err != nil {
			return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
		}

		if len(list) > 0 {
			for _, v := range list {
				titleRune := []rune(v.Title)
				if len(titleRune) > 20 {
					titleMap[v.TopicID] = string(titleRune[0:17]) + "..."
				} else {
					titleMap[v.TopicID] = v.Title
				}
			}
		}
	}

	tx, err := l.svcCtx.DB.BeginTx(l.ctx, nil)
	if err != nil {
		return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
	}

	now := time.Now() // 本地时间
	gmtNow := modelx.GetGmtTime(now)
	if status > 0 { // 审核
		logs := l.GetLogs(in, idPostMap, *in.Type, status)
		notices := l.GetNotices(in, idPostMap, titleMap, noticeType)
		err = l.svcCtx.Post.UpdateBatch(l.ctx, tx, pids, model.M{
			model.ForumPostColumns.PostStatus:       status,
			model.ForumPostColumns.PostModifiedDate: now,
			model.ForumPostColumns.PostModifiedGMT:  gmtNow,
		}, &model.ForumPostRel{
			Logs:    logs,
			Notices: notices,
		})
		if err != nil {
			tx.Rollback()
			return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
		}
	} else { // 推荐
		logs := l.GetLogs(in, idPostMap, *in.Type, sticky)
		notices := l.GetNotices(in, idPostMap, titleMap, noticeType)
		err = l.svcCtx.Post.UpdateBatch(l.ctx, tx, pids, model.M{
			model.ForumPostColumns.Sticky:           sticky,
			model.ForumPostColumns.PostModifiedDate: now,
			model.ForumPostColumns.PostModifiedGMT:  gmtNow,
		}, &model.ForumPostRel{
			Logs:    logs,
			Notices: notices,
		})
		if err != nil {
			tx.Rollback()
			return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
		}
	}

	if recMedia && len(strings.TrimSpace(*in.Uids)) > 0 { // 图片审核通过，推送图片到词条
		if vals := modelx.UniqueInterfaces(strings.TrimSpace(*in.Uids), 64); len(vals) > 0 {
			mediaFilterMap := make(map[string]map[string][]any)
			mediaFilterMap[model.ForumAttachmentRelationshipColumns.AttachmentRelationshipID] = map[string][]any{
				"in": vals,
			}
			mediaFilterMap[model.ForumAttachmentRelationshipColumns.ObjectType] = map[string][]any{
				"equalTo": []any{globalkey.AttachmentLogTypeForumPost},
			}
			list, _, err := l.svcCtx.Post.GetMedia(l.ctx, model.M{}, -1, mediaFilterMap)
			if err != nil {
				tx.Rollback()
				return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
			}

			var uids []uint64
			var media []*blogclient.TmpMediaInfo
			for _, v := range list {
				uids = append(uids, v.AttachmentRelationshipID)

				if pid, ok := idParentMap[v.ObjectID]; ok { // 香水ID
					if post, ok := idPostMap[v.ObjectID]; ok {
						author, err := json.Marshal(&blogclient.BlogAuthor{
							Id:   int64(post.PostAuthorID),
							Name: post.PostAuthorNickname,
							Url:  post.PostAuthorAvatar,
						})
						if err != nil {
							logrus.Info(fmt.Sprintf("BackendBatchUpdatePost json.Marshal blogclient.BlogAuthor err: %+v", err))
							tx.Rollback()
							return nil, err
						}
						media = append(media, &blogclient.TmpMediaInfo{
							Uid:      pointy.GetPointer(pid),
							Id:       pointy.GetPointer(strconv.FormatInt(int64(v.AttachmentID), 10)),
							Url:      &v.AttachmentImageURL,
							AltText:  pointy.GetPointer(strconv.FormatInt(int64(post.PostID), 10)),
							MetaData: pointy.GetPointer(string(author)),
						})
					}
				}
			}

			if len(media) > 0 {
				_, err = l.svcCtx.BlogRpc.SetKBPostMeta(l.ctx, &blogclient.SetKBPostMetaReq{
					Media: media,
				})
				if err != nil {
					logrus.Info(fmt.Sprintf("BackendBatchUpdatePost BlogRpc.BackendSetKBPostMeta err: %+v", err))
					tx.Rollback()
					return nil, err
				}
			}

			if len(uids) > 0 {
				_, err = l.svcCtx.Post.UpdateMediaBatch(l.ctx, tx, uids, model.M{
					model.ForumAttachmentRelationshipColumns.AttachmentThumbnail: 1,
				})
				if err != nil {
					tx.Rollback()
					return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
				}
			}
		}

		var meta []*userclient.UserMetaInfo
		for pid, authorId := range idAuthorMap {
			if count, ok := idCountMap[pid]; ok {
				meta = append(meta, &userclient.UserMetaInfo{
					Id:    pointy.GetPointer(authorId),
					Value: pointy.GetPointer(strconv.FormatInt(int64(count), 10)),
				})
			}
		}

		if len(meta) > 0 {
			l.svcCtx.UserRpc.SetUserMeta(l.ctx, &userclient.SetUserMetaReq{
				MetaKey:  pointy.GetPointer(mqueuex.UserStatisticForumPhotoPublicCount), // "_post_photo_public_count"
				Incr:     pointy.GetPointer(int32(1)),
				UserMeta: meta,
			})
		}

		return resp, nil
	}

	if recRating { // 香评审核通过，推送评分到词条
		metaFilterMap := make(map[string]map[string][]any)
		metaFilterMap[model.ForumPostMetumColumns.PostID] = map[string][]any{
			"in": ids,
		}
		list, _, err := l.svcCtx.Post.GetMeta(l.ctx, model.M{}, -1, metaFilterMap)
		if err != nil {
			tx.Rollback()
			return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
		}

		if len(list) > 0 {
			metaMap := make(map[string]string)
			for _, v := range list {
				if len(v.MetaKey) > 0 && v.MetaValue.Valid {
					key := v.MetaKey
					if key == "_grade" {
						metaMap[v.MetaValue.String] = key
					}
				}
			}
			idMetaMap := make(map[uint64][]*model.ForumPostMetum)
			for _, v := range list {
				if len(v.MetaKey) > 0 && v.MetaValue.Valid {
					key := v.MetaKey
					if key == "_grade" {
						continue
					}

					if _, ok := metaMap[key]; ok {
						if pid, ok := idParentMap[v.PostID]; ok { // 香水ID
							idMetaMap[pid] = append(idMetaMap[pid], v)
						}
					}
				}
			}
			var meta []*blogclient.MetaInfo
			for pid, ms := range idMetaMap {
				var reviewCount int64
				var count int64
				var total int64
				reviewIdMap := make(map[uint64]struct{})
				for _, v := range ms {
					if _, ok := reviewIdMap[v.PostID]; !ok {
						reviewCount += 1
						reviewIdMap[v.PostID] = struct{}{}
					}
					if i, err := strconv.ParseInt(v.MetaValue.String, 10, 32); err == nil {
						count += 1
						total += i
					}
				}
				if reviewCount > 0 {
					meta = append(meta, &blogclient.MetaInfo{
						Id:    pointy.GetPointer(pid),
						Key:   pointy.GetPointer("_review_count"),
						Value: pointy.GetPointer(strconv.FormatInt(reviewCount, 10)),
					})
				}
				if count > 0 {
					meta = append(meta, &blogclient.MetaInfo{
						Id:    pointy.GetPointer(pid),
						Key:   pointy.GetPointer("_rating_count"),
						Value: pointy.GetPointer(strconv.FormatInt(count, 10)),
					})
					meta = append(meta, &blogclient.MetaInfo{
						Id:    pointy.GetPointer(pid),
						Key:   pointy.GetPointer("_rating_total"),
						Value: pointy.GetPointer(strconv.FormatInt(total, 10)),
					})
				}
			}

			if len(meta) > 0 {
				_, err = l.svcCtx.BlogRpc.SetKBPostMeta(l.ctx, &blogclient.SetKBPostMetaReq{
					Meta: meta,
				})
				if err != nil {
					logrus.Info(fmt.Sprintf("BackendBatchUpdatePost BlogRpc.BackendSetKBPostMeta err: %+v", err))
					tx.Rollback()
					return nil, err
				}
			}

			if in.Gems != nil && len(*in.Gems) > 0 {
				gem, err := moneyx.ParseGem(*in.Gems)
				if err != nil {
					logrus.Info(fmt.Sprintf("BackendBatchUpdatePost failed to ParseGem err: %+v", err))
					tx.Rollback()
					return nil, err
				}

				var taskMeta []*userclient.UserMetaInfo
				for pid, authorId := range idAuthorMap {
					if post, ok := idPostMap[pid]; ok {
						if title, ok := titleMap[post.ParentID]; ok {
							taskMeta = append(taskMeta, &userclient.UserMetaInfo{
								Uid:   pointy.GetPointer(pid),
								Id:    pointy.GetPointer(authorId),
								Key:   pointy.GetPointer("任务奖励，发表香评:" + post.PostContent),
								Value: pointy.GetPointer("香水:" + title),
							})
						}
					}
				}
				if len(taskMeta) > 0 {
					_, err = l.svcCtx.UserRpc.SetMemberMeta(l.ctx, &userclient.SetMemberMetaReq{
						TaskName: pointy.GetPointer(mqueuex.UserTaskForumReview), // post_review
						TaskGem:  pointy.GetPointer(uint32(gem)),
						TaskMeta: taskMeta,
					})
					if err != nil {
						logrus.Info(fmt.Sprintf("BackendBatchUpdatePost UserRpc.SetMemberMeta err: %+v", err))
						tx.Rollback()
						return nil, err
					}
				}
			}
		}
	}

	if recFeed {
		for topicId, step := range idTopicMap {
			err = l.svcCtx.Topic.IncreaseMeta(l.ctx, tx, topicId, "_reply_count", step)
			if err != nil {
				logrus.Info(fmt.Sprintf("BackendBatchUpdatePost Topic.IncreaseMeta err: %+v", err))
				if err != model.ErrNotFound {
					tx.Rollback()
					return nil, err
				}
			}
		}

		if in.Gems != nil && len(*in.Gems) > 0 {
			gem, err := moneyx.ParseGem(*in.Gems)
			if err != nil {
				logrus.Info(fmt.Sprintf("BackendBatchUpdatePost failed to ParseGem err: %+v", err))
				tx.Rollback()
				return nil, err
			}

			var taskMeta []*userclient.UserMetaInfo
			for pid, authorId := range idAuthorMap {
				if post, ok := idPostMap[pid]; ok {
					if title, ok := titleMap[post.TopicID]; ok {
						taskMeta = append(taskMeta, &userclient.UserMetaInfo{
							Uid:   pointy.GetPointer(pid),
							Id:    pointy.GetPointer(authorId),
							Key:   pointy.GetPointer("任务奖励，发布回答:" + post.PostContent),
							Value: pointy.GetPointer("问题:" + title),
						})
					}
				}
			}
			if len(taskMeta) > 0 {
				_, err = l.svcCtx.UserRpc.SetMemberMeta(l.ctx, &userclient.SetMemberMetaReq{
					TaskName: pointy.GetPointer(mqueuex.UserTaskForumAnswer), // forum_reply
					TaskGem:  pointy.GetPointer(uint32(gem)),
					TaskMeta: taskMeta,
				})
				if err != nil {
					logrus.Info(fmt.Sprintf("BackendBatchUpdatePost UserRpc.SetMemberMeta err: %+v", err))
					tx.Rollback()
					return nil, err
				}
			}
		}
	}

	feedFilterMap := make(map[string]map[string][]any)
	feedFilterMap[model.ForumFeedColumns.PostID] = map[string][]any{
		"in": ids,
	}
	feedFilterMap[model.ForumFeedColumns.Status] = map[string][]any{
		"equalTo": []any{globalkey.StatusPending},
	}
	feeds, _, err := l.svcCtx.Post.GetFeeds(l.ctx, model.M{}, -1, feedFilterMap)
	if err != nil {
		logrus.Info(fmt.Sprintf("BackendBatchUpdatePost GetFeeds err: %+v", err))
	} else if len(feeds) > 0 {
		var fids []uint64
		for _, v := range feeds {
			fids = append(fids, v.ID)
		}

		_, err = l.svcCtx.Post.UpdateFeedBatch(l.ctx, tx, fids, model.M{
			model.ForumFeedColumns.Status: globalkey.StatusApproved,
		})
		if err != nil {
			tx.Rollback()
			return nil, errorhandler.DefaultRepositoryError(l.Logger, err, in)
		}
	}

	tx.Commit()

	return &forum.BaseResp{
		Msg: i18n.UpdateSuccess,
	}, nil
}

func (l *BackendBatchUpdatePostLogic) GetLogs(
	in *forum.BackendPostListReq,
	idPostMap map[uint64]*model.ForumPost,
	moderationType uint32,
	status int32,
) (res []*model.ForumPostModerationLog) {
	now := time.Now()
	for _, v := range idPostMap {
		var log model.ForumPostModerationLog
		log.PostAuthorID = v.PostAuthorID
		log.ForumID = v.ForumID
		log.ParentID = v.ParentID
		log.TopicID = v.TopicID
		log.PostID = v.PostID
		log.Type = uint8(moderationType)
		log.Status = uint8(status)
		if in.Memo != nil {
			log.Memo = *in.Memo
		}
		if in.Extra != nil {
			log.Extra = *in.Extra
		}
		if in.UpdatedBy != nil {
			log.UpdatedBy = *in.UpdatedBy
		}
		log.UpdatedAt = now
		res = append(res, &log)
	}

	return
}

func (l *BackendBatchUpdatePostLogic) GetNotices(
	in *forum.BackendPostListReq,
	idPostMap map[uint64]*model.ForumPost,
	titleMap map[uint64]string,
	noticeType int32,
) (res []*model.ForumUserNotice) {
	var status uint8 = 1 // 未读
	now := time.Now()
	for _, v := range idPostMap {
		var title string
		if parentTitle, ok := titleMap[v.ParentID]; ok {
			title = parentTitle
		}
		if len(title) == 0 {
			if topicTitle, ok := titleMap[v.TopicID]; ok {
				title = topicTitle
			}
		}

		var notice model.ForumUserNotice
		notice.ForumID = v.ForumID
		notice.TopicID = v.TopicID
		notice.ParentID = v.ParentID
		notice.UserID = v.PostAuthorID
		notice.PostID = v.PostID
		notice.Type = uint16(noticeType)
		notice.Title = title
		notice.Description = v.PostContent
		if in.Memo != nil {
			notice.Memo = *in.Memo
		}
		if in.Extra != nil {
			notice.Extra = *in.Extra
		}
		notice.Status = status
		notice.CreatedAt = now
		notice.UpdatedAt = now
		res = append(res, &notice)
	}

	return
}
